/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2020  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PrecompiledHeader.h"
//#include "Mixer.h"
#include "Global.h"
#include "SndOut.h"

#define _WIN32_DCOM
#ifdef _WIN32
#include <portaudio/include/portaudio.h>
#include "Windows/Dialogs.h"
#elif defined __linux
#include "portaudio.h"
#include "Linux/Dialogs.h"
#endif



#include "wchar.h"

#include <vector>

#ifdef _WIN32
#include <portaudio/include/pa_win_wasapi.h>
#endif
#include "Debug.h"

int PaCallback(const void* inputBuffer, void* outputBuffer,
			   unsigned long framesPerBuffer,
			   const PaStreamCallbackTimeInfo* timeInfo,
			   PaStreamCallbackFlags statusFlags,
			   void* userData);

class Portaudio : public SndOutModule
{
private:
	//////////////////////////////////////////////////////////////////////////////////////////
	// Configuration Vars (unused still)

	int m_ApiId;
	wxString m_Device;

	bool m_UseHardware;

	bool m_WasapiExclusiveMode;

	bool m_SuggestedLatencyMinimal;
	int m_SuggestedLatencyMS;

	//////////////////////////////////////////////////////////////////////////////////////////
	// Instance vars

	int writtenSoFar;
	int writtenLastTime;
	int availableLastTime;

	int actualUsedChannels;

	bool started;
	PaStream* stream;

	//////////////////////////////////////////////////////////////////////////////////////////
	// Stuff necessary for speaker expansion
	class SampleReader
	{
	public:
		virtual int ReadSamples(const void* inputBuffer, void* outputBuffer,
								unsigned long framesPerBuffer,
								const PaStreamCallbackTimeInfo* timeInfo,
								PaStreamCallbackFlags statusFlags,
								void* userData) = 0;
	};

	template <class T>
	class ConvertedSampleReader : public SampleReader
	{
		int* written;

	public:
		ConvertedSampleReader(int* pWritten)
		{
			written = pWritten;
		}

		virtual int ReadSamples(const void* inputBuffer, void* outputBuffer,
								unsigned long framesPerBuffer,
								const PaStreamCallbackTimeInfo* timeInfo,
								PaStreamCallbackFlags statusFlags,
								void* userData)
		{
			T* p1 = (T*)outputBuffer;

			int packets = framesPerBuffer / SndOutPacketSize;

			for (int p = 0; p < packets; p++, p1 += SndOutPacketSize)
				SndBuffer::ReadSamples(p1);

			(*written) += packets * SndOutPacketSize;

			return 0;
		}
	};

public:
	SampleReader* ActualPaCallback;

	Portaudio()
	{
		m_SuggestedLatencyMinimal = true;
		m_UseHardware = false;
		m_WasapiExclusiveMode = false;
		started = false;
		stream = nullptr;
		ActualPaCallback = nullptr;
		m_ApiId = -1;
		m_SuggestedLatencyMS = 20;
		actualUsedChannels = 0;
		writtenSoFar = 0;
		writtenLastTime = 0;
		availableLastTime = 0;
	}

	s32 Init()
	{
		started = false;
		stream = nullptr;

		ReadSettings();

		PaError err = Pa_Initialize();
		if (err != paNoError)
		{
			fprintf(stderr, "* SPU2: PortAudio error: %s\n", Pa_GetErrorText(err));
			return -1;
		}
		started = true;

		int deviceIndex = -1;

		fprintf(stderr, "* SPU2: Enumerating PortAudio devices:\n");
		for (int i = 0, j = 0; i < Pa_GetDeviceCount(); i++)
		{
			const PaDeviceInfo* info = Pa_GetDeviceInfo(i);

			if (info->maxOutputChannels > 0)
			{
				const PaHostApiInfo* apiinfo = Pa_GetHostApiInfo(info->hostApi);

				fprintf(stderr, " *** Device %d: '%s' (%s)", j, info->name, apiinfo->name);

				if (apiinfo->type == m_ApiId)
				{
					if (m_Device == wxString::FromUTF8(info->name))
					{
						deviceIndex = i;
						fprintf(stderr, " (selected)");
					}
				}
				fprintf(stderr, "\n");

				j++;
			}
		}
		fflush(stderr);

		if (deviceIndex < 0 && m_ApiId >= 0)
		{
			for (int i = 0; i < Pa_GetHostApiCount(); i++)
			{
				const PaHostApiInfo* apiinfo = Pa_GetHostApiInfo(i);
				if (apiinfo->type == m_ApiId)
				{
					deviceIndex = apiinfo->defaultOutputDevice;
				}
			}
		}

		if (deviceIndex >= 0)
		{
			void* infoPtr = nullptr;

			const PaDeviceInfo* devinfo = Pa_GetDeviceInfo(deviceIndex);

			int speakers;
			switch (numSpeakers) // speakers = (numSpeakers + 1) *2; ?
			{
				case 0:
					speakers = 2;
					break; // Stereo
				case 1:
					speakers = 4;
					break; // Quadrafonic
				case 2:
					speakers = 6;
					break; // Surround 5.1
				case 3:
					speakers = 8;
					break; // Surround 7.1
				default:
					speakers = 2;
			}
			actualUsedChannels = std::min(speakers, devinfo->maxOutputChannels);

			switch (actualUsedChannels)
			{
				case 2:
					ConLog("* SPU2 > Using normal 2 speaker stereo output.\n");
					ActualPaCallback = new ConvertedSampleReader<Stereo20Out32>(&writtenSoFar);
					break;

				case 3:
					ConLog("* SPU2 > 2.1 speaker expansion enabled.\n");
					ActualPaCallback = new ConvertedSampleReader<Stereo21Out32>(&writtenSoFar);
					break;

				case 4:
					ConLog("* SPU2 > 4 speaker expansion enabled [quadraphenia]\n");
					ActualPaCallback = new ConvertedSampleReader<Stereo40Out32>(&writtenSoFar);
					break;

				case 5:
					ConLog("* SPU2 > 4.1 speaker expansion enabled.\n");
					ActualPaCallback = new ConvertedSampleReader<Stereo41Out32>(&writtenSoFar);
					break;

				case 6:
				case 7:
					switch (dplLevel)
					{
						case 0:
							ConLog("* SPU2 > 5.1 speaker expansion enabled.\n");
							ActualPaCallback = new ConvertedSampleReader<Stereo51Out32>(&writtenSoFar); //"normal" stereo upmix
							break;
						case 1:
							ConLog("* SPU2 > 5.1 speaker expansion with basic ProLogic dematrixing enabled.\n");
							ActualPaCallback = new ConvertedSampleReader<Stereo51Out32Dpl>(&writtenSoFar); // basic Dpl decoder without rear stereo balancing
							break;
						case 2:
							ConLog("* SPU2 > 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.\n");
							ActualPaCallback = new ConvertedSampleReader<Stereo51Out32DplII>(&writtenSoFar); //gigas PLII
							break;
					}
					actualUsedChannels = 6; // we do not support 7.0 or 6.2 configurations, downgrade to 5.1
					break;

				default: // anything 8 or more gets the 7.1 treatment!
					ConLog("* SPU2 > 7.1 speaker expansion enabled.\n");
					ActualPaCallback = new ConvertedSampleReader<Stereo71Out32>(&writtenSoFar);
					actualUsedChannels = 8; // we do not support 7.2 or more, downgrade to 7.1
					break;
			}

#ifdef _WIN32
			PaWasapiStreamInfo info = {
				sizeof(PaWasapiStreamInfo),
				paWASAPI,
				1,
				paWinWasapiExclusive};

			if ((m_ApiId == paWASAPI) && m_WasapiExclusiveMode)
			{
				// Pass it the Exclusive mode enable flag
				infoPtr = &info;
			}
#endif

			PaStreamParameters outParams = {

				//	PaDeviceIndex device;
				//	int channelCount;
				//	PaSampleFormat sampleFormat;
				//	PaTime suggestedLatency;
				//	void *hostApiSpecificStreamInfo;
				deviceIndex,
				actualUsedChannels,
				paInt32,
				m_SuggestedLatencyMinimal ? (SndOutPacketSize / (float)SampleRate) : (m_SuggestedLatencyMS / 1000.0f),
				infoPtr};

			err = Pa_OpenStream(&stream,
								nullptr, &outParams, SampleRate,
								SndOutPacketSize,
								paNoFlag,
								PaCallback,

								nullptr);
		}
		else
		{
			err = Pa_OpenDefaultStream(&stream,
									   0, actualUsedChannels, paInt32, 48000,
									   SndOutPacketSize,
									   PaCallback,
									   nullptr);
		}
		if (err != paNoError)
		{
			fprintf(stderr, "* SPU2: PortAudio error: %s\n", Pa_GetErrorText(err));
			Pa_Terminate();
			return -1;
		}

		err = Pa_StartStream(stream);
		if (err != paNoError)
		{
			fprintf(stderr, "* SPU2: PortAudio error: %s\n", Pa_GetErrorText(err));
			Pa_CloseStream(stream);
			stream = nullptr;
			Pa_Terminate();
			return -1;
		}

		return 0;
	}

	void Close()
	{
		PaError err;
		if (started)
		{
			if (stream)
			{
				if (Pa_IsStreamActive(stream))
				{
					err = Pa_StopStream(stream);
					if (err != paNoError)
						fprintf(stderr, "* SPU2: PortAudio error: %s\n", Pa_GetErrorText(err));
				}

				err = Pa_CloseStream(stream);
				if (err != paNoError)
					fprintf(stderr, "* SPU2: PortAudio error: %s\n", Pa_GetErrorText(err));

				stream = nullptr;
			}

			// Seems to do more harm than good.
			//PaError err = Pa_Terminate();
			//if( err != paNoError )
			//	fprintf(stderr,"* SPU2: PortAudio error: %s\n", Pa_GetErrorText( err ) );

			started = false;
		}
	}

////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
#ifdef _WIN32
private:
	bool _ConfigProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		int wmId, wmEvent;
		int tSel = 0;

		switch (uMsg)
		{
			case WM_INITDIALOG:
			{
				wchar_t temp[128];

				SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_RESETCONTENT, 0, 0);
				SendMessageA(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_ADDSTRING, 0, (LPARAM) "Default Device");
				SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETCURSEL, 0, 0);

				SendMessage(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_RESETCONTENT, 0, 0);
				SendMessageA(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_ADDSTRING, 0, (LPARAM) "Unspecified");
				int idx = 0;
				for (int i = 0; i < Pa_GetHostApiCount(); i++)
				{
					const PaHostApiInfo* apiinfo = Pa_GetHostApiInfo(i);
					if (apiinfo->deviceCount > 0)
					{
						SendMessageA(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_ADDSTRING, 0, (LPARAM)apiinfo->name);
						SendMessageA(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_SETITEMDATA, i + 1, apiinfo->type);
					}
					if (apiinfo->type == m_ApiId)
					{
						idx = i + 1;
					}
				}
				SendMessage(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_SETCURSEL, idx, 0);

				if (idx > 0)
				{
					int api_idx = idx - 1;
					SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_RESETCONTENT, 0, 0);
					SendMessageA(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_ADDSTRING, 0, (LPARAM) "Default Device");
					SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETITEMDATA, 0, 0);
					int _idx = 0;
					int i = 1;
					for (int j = 0; j < Pa_GetDeviceCount(); j++)
					{
						const PaDeviceInfo* info = Pa_GetDeviceInfo(j);
						if (info->hostApi == api_idx && info->maxOutputChannels > 0)
						{
							SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_ADDSTRING, 0, (LPARAM)wxString::FromUTF8(info->name).wc_str());
							SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETITEMDATA, i, (LPARAM)info);
							if (wxString::FromUTF8(info->name) == m_Device)
							{
								_idx = i;
							}
							i++;
						}
					}
					SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETCURSEL, _idx, 0);
				}

				INIT_SLIDER(IDC_LATENCY, 10, 200, 10, 1, 1);
				SendMessage(GetDlgItem(hWnd, IDC_LATENCY), TBM_SETPOS, TRUE, m_SuggestedLatencyMS);
				swprintf_s(temp, L"%d ms", m_SuggestedLatencyMS);
				SetWindowText(GetDlgItem(hWnd, IDC_LATENCY_LABEL), temp);

				if (m_SuggestedLatencyMinimal)
					SET_CHECK(IDC_MINIMIZE, true);
				else
					SET_CHECK(IDC_MANUAL, true);

				SET_CHECK(IDC_EXCLUSIVE, m_WasapiExclusiveMode);
			}
			break;

			case WM_COMMAND:
			{
				//wchar_t temp[128];

				wmId = LOWORD(wParam);
				wmEvent = HIWORD(wParam);
				// Parse the menu selections:
				switch (wmId)
				{
					case IDOK:
					{
						int idx = (int)SendMessage(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_GETCURSEL, 0, 0);
						m_ApiId = SendMessage(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_GETITEMDATA, idx, 0);

						idx = (int)SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_GETCURSEL, 0, 0);
						const PaDeviceInfo* info = (const PaDeviceInfo*)SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_GETITEMDATA, idx, 0);
						if (info)
							m_Device = wxString::FromUTF8(info->name);
						else
							m_Device = L"default";

						m_SuggestedLatencyMS = (int)SendMessage(GetDlgItem(hWnd, IDC_LATENCY), TBM_GETPOS, 0, 0);

						if (m_SuggestedLatencyMS < 10)
							m_SuggestedLatencyMS = 10;
						if (m_SuggestedLatencyMS > 200)
							m_SuggestedLatencyMS = 200;

						m_SuggestedLatencyMinimal = SendMessage(GetDlgItem(hWnd, IDC_MINIMIZE), BM_GETCHECK, 0, 0) == BST_CHECKED;

						m_WasapiExclusiveMode = SendMessage(GetDlgItem(hWnd, IDC_EXCLUSIVE), BM_GETCHECK, 0, 0) == BST_CHECKED;

						EndDialog(hWnd, 0);
					}
					break;

					case IDCANCEL:
						EndDialog(hWnd, 0);
						break;

					case IDC_PA_HOSTAPI:
					{
						if (wmEvent == CBN_SELCHANGE)
						{
							int api_idx = (int)SendMessage(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_GETCURSEL, 0, 0) - 1;
							int apiId = SendMessageA(GetDlgItem(hWnd, IDC_PA_HOSTAPI), CB_GETITEMDATA, api_idx, 0);
							SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_RESETCONTENT, 0, 0);
							SendMessageA(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_ADDSTRING, 0, (LPARAM) "Default Device");
							SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETITEMDATA, 0, 0);
							int idx = 0;
							int i = 1;
							for (int j = 0; j < Pa_GetDeviceCount(); j++)
							{
								const PaDeviceInfo* info = Pa_GetDeviceInfo(j);
								if (info->hostApi == api_idx && info->maxOutputChannels > 0)
								{
									SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_ADDSTRING, 0, (LPARAM)wxString::FromUTF8(info->name).wc_str());
									SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETITEMDATA, i, (LPARAM)info);
									i++;
								}
							}
							SendMessage(GetDlgItem(hWnd, IDC_PA_DEVICE), CB_SETCURSEL, idx, 0);
						}
					}
					break;

					default:
						return FALSE;
				}
			}
			break;

			case WM_HSCROLL:
			{
				wmId = LOWORD(wParam);
				wmEvent = HIWORD(wParam);
				switch (wmId)
				{
					case TB_LINEUP:
					case TB_LINEDOWN:
					case TB_PAGEUP:
					case TB_PAGEDOWN:
					case TB_TOP:
					case TB_BOTTOM:
						wmEvent = (int)SendMessage((HWND)lParam, TBM_GETPOS, 0, 0);
					case TB_THUMBPOSITION:
					case TB_THUMBTRACK:
					{
						wchar_t temp[128];
						if (wmEvent < 10)
							wmEvent = 10;
						if (wmEvent > 200)
							wmEvent = 200;
						SendMessage((HWND)lParam, TBM_SETPOS, TRUE, wmEvent);
						swprintf_s(temp, L"%d ms", wmEvent);
						SetWindowText(GetDlgItem(hWnd, IDC_LATENCY_LABEL), temp);
						break;
					}
					default:
						return FALSE;
				}
			}
			break;

			default:
				return FALSE;
		}
		return TRUE;
	}

	static BOOL CALLBACK ConfigProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext);

public:
	virtual void Configure(uptr parent)
	{
		PaError err = Pa_Initialize(); // Initialization can be done multiple times, PA keeps a counter
		if (err != paNoError)
		{
			fprintf(stderr, "* SPU2: PortAudio error: %s\n", Pa_GetErrorText(err));
			return;
		}
		// keep portaudio initialized until the dialog closes

		INT_PTR ret;
		ret = DialogBoxParam(nullptr, MAKEINTRESOURCE(IDD_PORTAUDIO), (HWND)parent, (DLGPROC)ConfigProc, 1);
		if (ret == -1)
		{
			MessageBox((HWND)parent, L"Error Opening the config dialog.", L"OMG ERROR!", MB_OK | MB_SETFOREGROUND);
			return;
		}

		Pa_Terminate();
	}
#else
	virtual void Configure(uptr parent)
	{
	}
#endif

	s32 Test() const
	{
		return 0;
	}

	int GetEmptySampleCount()
	{
		long availableNow = Pa_GetStreamWriteAvailable(stream);

		int playedSinceLastTime = (writtenSoFar - writtenLastTime) + (availableNow - availableLastTime);
		writtenLastTime = writtenSoFar;
		availableLastTime = availableNow;

		// Lowest resolution here is the SndOutPacketSize we use.
		return playedSinceLastTime;
	}

	const wchar_t* GetIdent() const
	{
		return L"portaudio";
	}

	const wchar_t* GetLongName() const
	{
		return L"PortAudio (Cross-platform)";
	}

	void ReadSettings()
	{
		wxString api(L"EMPTYEMPTYEMPTY");
		m_Device = L"EMPTYEMPTYEMPTY";
#ifdef __linux__
		// By default on linux use the ALSA API (+99% users) -- Gregory
		CfgReadStr(L"PORTAUDIO", L"HostApi", api, L"ALSA");
#elif defined(__APPLE__)
		// Suppose OSX only has CoreAudio...
		CfgReadStr(L"PORTAUDIO", L"HostApi", api, L"CoreAudio");
#else
		CfgReadStr(L"PORTAUDIO", L"HostApi", api, L"WASAPI");
#endif
		CfgReadStr(L"PORTAUDIO", L"Device", m_Device, L"default");

		SetApiSettings(api);

		m_WasapiExclusiveMode = CfgReadBool(L"PORTAUDIO", L"Wasapi_Exclusive_Mode", false);
		m_SuggestedLatencyMinimal = CfgReadBool(L"PORTAUDIO", L"Minimal_Suggested_Latency", true);
		m_SuggestedLatencyMS = CfgReadInt(L"PORTAUDIO", L"Manual_Suggested_Latency_MS", 20);

		if (m_SuggestedLatencyMS < 10)
			m_SuggestedLatencyMS = 10;
		if (m_SuggestedLatencyMS > 200)
			m_SuggestedLatencyMS = 200;
	}

	void SetApiSettings(wxString api)
	{
		m_ApiId = -1;
		if (api == L"InDevelopment")
			m_ApiId = paInDevelopment; /* use while developing support for a new host API */
		if (api == L"DirectSound")
			m_ApiId = paDirectSound;
		if (api == L"MME")
			m_ApiId = paMME;
		if (api == L"ASIO")
			m_ApiId = paASIO;
		if (api == L"SoundManager")
			m_ApiId = paSoundManager;
		if (api == L"CoreAudio")
			m_ApiId = paCoreAudio;
		if (api == L"OSS")
			m_ApiId = paOSS;
		if (api == L"ALSA")
			m_ApiId = paALSA;
		if (api == L"AL")
			m_ApiId = paAL;
		if (api == L"BeOS")
			m_ApiId = paBeOS;
		if (api == L"WDMKS")
			m_ApiId = paWDMKS;
		if (api == L"JACK")
			m_ApiId = paJACK;
		if (api == L"WASAPI")
			m_ApiId = paWASAPI;
		if (api == L"AudioScienceHPI")
			m_ApiId = paAudioScienceHPI;
	}

	void WriteSettings() const
	{
		wxString api;
		switch (m_ApiId)
		{
			case paInDevelopment:
				api = L"InDevelopment";
				break; /* use while developing support for a new host API */
			case paDirectSound:
				api = L"DirectSound";
				break;
			case paMME:
				api = L"MME";
				break;
			case paASIO:
				api = L"ASIO";
				break;
			case paSoundManager:
				api = L"SoundManager";
				break;
			case paCoreAudio:
				api = L"CoreAudio";
				break;
			case paOSS:
				api = L"OSS";
				break;
			case paALSA:
				api = L"ALSA";
				break;
			case paAL:
				api = L"AL";
				break;
			case paBeOS:
				api = L"BeOS";
				break;
			case paWDMKS:
				api = L"WDMKS";
				break;
			case paJACK:
				api = L"JACK";
				break;
			case paWASAPI:
				api = L"WASAPI";
				break;
			case paAudioScienceHPI:
				api = L"AudioScienceHPI";
				break;
			default:
				api = L"Unknown";
		}

		CfgWriteStr(L"PORTAUDIO", L"HostApi", api);
		CfgWriteStr(L"PORTAUDIO", L"Device", m_Device);

		CfgWriteBool(L"PORTAUDIO", L"Wasapi_Exclusive_Mode", m_WasapiExclusiveMode);
		CfgWriteBool(L"PORTAUDIO", L"Minimal_Suggested_Latency", m_SuggestedLatencyMinimal);
		CfgWriteInt(L"PORTAUDIO", L"Manual_Suggested_Latency_MS", m_SuggestedLatencyMS);
	}

} static PA;

int PaCallback(const void* inputBuffer, void* outputBuffer,
			   unsigned long framesPerBuffer,
			   const PaStreamCallbackTimeInfo* timeInfo,
			   PaStreamCallbackFlags statusFlags,
			   void* userData)
{
	return PA.ActualPaCallback->ReadSamples(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags, userData);
}

#ifdef _WIN32
BOOL CALLBACK Portaudio::ConfigProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return PA._ConfigProc(hWnd, uMsg, wParam, lParam);
}
#endif

SndOutModule* PortaudioOut = &PA;
